import os
import struct
import copy
import Image

import DataExtraction.BSAHeaderDFA
import DataExtraction.BSAIndex
import DataExtraction.GlobalBSA
import Utils
import Utils.gifmaker

class BSADFA:
    """DFAs are a form of animation, but simpler than CFA.  Whereas CFAs have a
       separate image per frame, DFAs store a single base frame with animation
       changing a portion of it (e.g., the bartender sprite only animates the
       hand).  However, some DFAs completely replace the image contents every
       frame (the crystal ball and some guard sprites completely clear the image
       for some frames).

      The base frame is encoded using only RLE.  This is immediately followed
      by a block of replacement values encoded as a byte offset + new color."""

    def __init__(self, entry = None, global_bsa_data = None,
                 default_palette_red_first = None):
        
        self._header = DataExtraction.BSAHeaderDFA.BSAHeaderDFA()
        self._global_bsa_data = global_bsa_data
        self._default_palette_red_first = default_palette_red_first
        self._offset = entry.get_offset()
        self._fname = entry.get_name()
        self._frames = []
        
    # getters
    def get_header(self):
        return self._header
    
    def get_global_bsa_data(self):
        return self._global_bsa_data
    
    def get_offset(self):
        return self._offset
    
    def get_filename(self):
        return self._fname
    
    def get_frames(self):
        return self._frames
    
    # setters
    def set_header(self, header):
        self._header = header
        
    def set_global_bsa_data(self, global_bsa_data):
        self._global_bsa_data = global_bsa_data
        
    def set_offset(self, offset):
        self._offset = offset
        
    def set_filename(self, filename):
        self._fname = filename
    
    def set_frames(self, frames):
        self._frames = frames

    def _build_next_frame(self, offset, chunk_count, block_fmt, unrle_data):
        # Skip over the header.  We're now looking at the first
        # chunk of replacement colors.
        offset += 4
        
        # Loop over each chunk.  This will contain a count of pixels,
        # and a byte offset relative to the start of the image, followed
        # immediately by a line of pixels that replace a chunk of
        # pixels in the previous image.
        for chunk_num in xrange(chunk_count):
            self._global_bsa_data.seek(self._offset + offset, os.SEEK_SET)
            r = self._global_bsa_data.read(struct.calcsize(block_fmt))
            
            replace_offset, replace_count = struct.unpack(block_fmt, r)
            
            offset += 4
            
            replace_fmt = 'B'
            for i in xrange(replace_count):
                # seek to the ith unsigned char (BYTE)
                self._global_bsa_data.seek(self._offset + offset, os.SEEK_SET)
                
                r = self._global_bsa_data.read(struct.calcsize(replace_fmt))
                val = struct.unpack(replace_fmt, r)[0]
                
                unrle_data[replace_offset + i] = val
                offset += 1
        
        return copy.deepcopy(unrle_data)
    
    def load(self):
        # load the header
        self._header.load(self._global_bsa_data, self._offset)
        
        width = self._header.get_width()
        height = self._header.get_height()
        compressed_size = self._header.get_compressed_size()
            
        pitch = self._header.get_width() + 1
        
        self._data_offset = self._offset + self._header.get_size_in_bytes()
        
        # Removing the RLE will provide the base image data.  Unlike CFAs,
        # nothing more is needed to make this frame useful.
        self._global_bsa_data.seek(self._data_offset, os.SEEK_SET)
        
        bytes_to_read = 0
        
        if width * height == compressed_size:
            bytes_to_read = width * height
        else:
            bytes_to_read = compressed_size

        rle_str = self._global_bsa_data.read(bytes_to_read)
        
        rle_fmt = (bytes_to_read) * 'B'
        
        rle_data = struct.unpack(rle_fmt, rle_str)
        
        unrle_data, cs = Utils.Utils.rle_decode(rle_data, width * height)

        unrle_data_copy = copy.deepcopy(unrle_data)
        
        self._global_bsa_data.seek(self._offset, os.SEEK_SET)
        self._frames.append(unrle_data_copy)
        # Take the 12 bytes of the header, plus the size of the compressed
        # data.  This gives an offset to the start of the replacement values.
        offset = 12 + compressed_size
        
        # Replacement data takes the form of chunks, each chunk containing
        # the changes required for a group of pixels.  Each frame (or block)
        # uses one or more chunks of replacement values.
        frame_num = self._header.get_frame_count()
        
        block_fmt = 2 * 'H'
        
        for block_num in xrange(1, frame_num):
            self._global_bsa_data.seek(self._offset + offset, os.SEEK_SET)
            
            r = self._global_bsa_data.read(struct.calcsize(block_fmt))
            chunk_size, chunk_count = struct.unpack(block_fmt, r)
            
            unrle_data2 = copy.deepcopy(unrle_data)
            unrle_data_copy2 = \
                    self._build_next_frame(offset, chunk_count, block_fmt, unrle_data2)
            
            # If this not the frame we want, skip all of the chunks in it.
            # All changes are relative to the original frame, not to the
            # previous frame.
            if block_num < frame_num:
                offset += 2 + chunk_size
            else:
                unrle_data = copy.deepcopy(unrle_data2)
                
            # Once the modified frame has been established, blit the individual
            # lines into the destination buffer, accounting for the image's pitch.     
            out_buffer = [0 for x in xrange((width + 1) * (height + 1))]

            for y in xrange(height):
                src_off = y * width
                dst_off = y * pitch
                for x in xrange(width):
                    out_buffer[dst_off + x] = unrle_data[src_off + x]
                    
            # let's save the frame
            self._frames.append(unrle_data_copy2)            
    
    def dump(self, filename):
        gif_seq = []

        pal = self._default_palette_red_first
        # create some gifs from the frames
        for (index, frame) in enumerate(self._frames):
            gif_image = Image.new('P', ( self._header.get_width(),
                                         self._header.get_height() ) )

            gif_image.putpalette(pal)
            gif_image.format = 'GIF'

            gif_image.putdata(frame)
            
            gif_seq.append(gif_image)
            #print gif_image.format, gif_image.size, gif_image.mode
        
        out_gif = open(filename, 'wb')
        Utils.gifmaker.makedelta(out_gif, gif_seq, 250, pal, self._frames)
        out_gif.close()